from .common import *
from JDjango.api import model_type_data
from JDjango.api import retools
from JDjango.api import djangotools
from ..MessageDialog import RichMsgDialog
from copy import deepcopy
from JDjango import validators

'''
    EnumProperty 类型的 数据包会在运行时添加 一个 choices_data 列表类型属性。
    用于列表索引取值。


    【可用于模板填充的模板值】
    app_names: 所有的应用程序名
    this_model_names: 当前应用程序下的所有模型名称
    model_names: 所有的应用程序下的所有的模型名称
    data_fields: 所有日期类型的字段名
    primary_fields: 所有主键字段名
    foreignkey_fields: 所有一对多外键字段名
    fields: 当前模型的所有字段名

    【目前暂不支持动态校验规则，可能在后续版本支持】

'''

CHOICES_DATA = "choices_data" # 扩展的数据包键名【用于存放 EnumProperty 的中文列表】
'''
    EnumProperty 的中文辅助显示键索引名称
'''
MODEL_NAMES = "model_names"
DATA_FIELDS = "data_fields"

class FieldCreateDialog(wx.Dialog):
    def __init__(self, parent
        , field_name: str # 当前选中的 树形列表 的节点名，如：关系型|一对多
        , app_name: str # 当前选中应用程序名
        , model_name: str # 当前选中模型名
        , model_path: str # 当前选中的模型路径
        , context_app_names_models: Dict[str, List[str]] # 所有的应用程序名及其所属的所有模型名
        , model_fields: Dict[str, Dict[str, Any]]): # 当前选中模型下的所有字段集合

        wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = f'新增【{field_name}】字段', size=(555, 630), style=wx.DEFAULT_DIALOG_STYLE|wx.MAXIMIZE_BOX|wx.MINIMIZE_BOX|wx.RESIZE_BORDER)

        self.field_name = field_name # 字段的类型汉字提示信息。如：自增正整型|16位
        self.app_name = app_name
        self.model_name = model_name
        self.model_path = model_path
        self.context_app_names_models = context_app_names_models
        self.model_fields = model_fields

        self._init_data()
        self._init_UI()
        self._init_listener()

    def _init_data(self):
        '''初始化数据'''
        self.options: Dict[str, Any] = None # 所有的通用选项
        self.field_type: Dict[str, Any] = None # 所有的额外选项的字段类型集合
        self.oriKeyName_combineName: Dict[str, Any] = {}

        '''
            用于操作的数据集：可修改。主要用于数据的修改。【实际没用到，作为后续功能实现】
        '''
        self.kwargs_datas: Dict[str, Any] = None

        '''
            最初的数据存留【深拷贝】：用于最后的关键字参数和位置参数的生成【比对用】。
        '''
        self.deepcopy_kwargs_datas: Dict[str, Any] = None

        '''
            模板填充值【用于在json数据中使用模板替换功能】
        '''
        self._init_template_values()

    def _init_template_values(self):
        '''初始化模板填充值

            app_names: 所有的应用程序名
            this_model_names: 当前应用程序下的所有模型名称
            model_names: 所有的应用程序下的所有的模型名称
            data_fields: 所有日期类型的字段名
            primary_fields: 所有主键字段名
            foreignkey_fields: 所有一对多外键字段名
            fields: 当前模型的所有字段名
        
            依据：self.context_app_names_models、self.model_fields

            依赖于模板替换语法。
        '''
        self.t_app_names: List[str] = []
        self.t_this_model_names: List[str] = []
        self.t_model_names: List[str] = []
        self.t_data_fields: List[str] = []
        self.t_primary_fields: List[str] = []
        self.t_foreignkey_fields: List[str] = []
        self.t_fields: List[str] = [] # 当前的模型的已有的字段集合

        for _app_name, _model_names, _model_path in self.context_app_names_models:
            self.t_app_names.append(_app_name)
            for _model_name in _model_names:
                if _app_name == self.app_name:
                    self.t_this_model_names.append(_model_name)
                    self.t_model_names.append(_model_name)
                else:
                    self.t_model_names.append(f"{_app_name}.{_model_name}") # 跨应用程序指定模型

        for k, v in self.model_fields.items():
            self.t_fields.append(k)
            if v["type_name"] in ("DateField", "DateTimeField", "TimeField", "DurationField"):
                self.t_data_fields.append(k)
            if v["options"].get("primary_key") == True:
                self.t_primary_fields.append(k)
            if v["type_name"] in ("ForeignKey", "ManyToManyField", "OneToOneField"):
                self.t_foreignkey_fields.append(k)

        self.ALL_TEMPLATES_ARGS = {
            MODEL_NAMES: self.t_model_names,
            DATA_FIELDS: self.t_data_fields
        }

    def _init_listener(self):
        '''注册监听'''
        self.Bind(wx.EVT_TEXT, self.onInputFieldName, self.inputFieldName)
        self.Bind(wx.EVT_BUTTON, self.onBtnSave, self.btnSave)
        self.Bind(wxpg.EVT_PG_SELECTED, self.onPgSelected, self.pgPanel)

    def _init_UI(self):
        """初始化界面布局"""
        self.panel = wx.Panel(self)
        self.panelSizer = wx.BoxSizer(wx.VERTICAL)
        self.panel.SetSizer(self.panelSizer)
        # self.panel.SetBackgroundColour(CON_COLOR_MAIN)

        # self.scollPanel = scrolledpanel.ScrolledPanel(self.panel, -1) # 定义滚动面板
        # self.scollPanel.SetupScrolling() # 开启滚动
        # scollPanelSizer = wx.BoxSizer(wx.VERTICAL)
        # self.scollPanel.SetSizer(scollPanelSizer)
        # self.panelSizer.Add(self.scollPanel, 3, wx.EXPAND | wx.ALL, 2) # 加入主容器
        info = f"正在为【{self.app_name}】应用程序下的【{self.model_name}】模型创建【{self.field_name}】字段"
        static_info = wx.StaticText(self.panel, -1, info)
        static_info.SetForegroundColour(wx.RED)
        self.panelSizer.Add(static_info, 0, wx.EXPAND | wx.ALL, 5)
        '''
            工具栏
        '''
        self.flexGridSizer = wx.FlexGridSizer(cols=3, hgap=5, vgap=5)
        self.panelSizer.Add(self.flexGridSizer, 0, wx.EXPAND | wx.ALL, 5)
        self.flexGridSizer.AddGrowableCol(1) # 第二列自适应大小

        self.inputFieldName = wx.TextCtrl(self.panel, -1, "", validator=validators.AlphaOnlyValidator())
        self.btnSave = buttons.GenButton(self.panel, -1, '创建')
        self.btnSave.SetBackgroundColour(CON_COLOR_MAIN)
        self.btnSave.SetForegroundColour(CON_COLOR_PURE_WHITE)
        
        self.flexGridSizer.Add(wx.StaticText(self.panel, -1, '字段命名：'), 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        self.flexGridSizer.Add(self.inputFieldName, 0, wx.EXPAND)
        self.flexGridSizer.Add(self.btnSave, 0, wx.EXPAND)

        '''
            属性面板
        '''
        self.pgPanel = wxpg.PropertyGridManager(
            self.panel,
            style = wxpg.PG_BOLD_MODIFIED # 修改后的值自动加粗
                | wxpg.PG_SPLITTER_AUTO_CENTER # 自动居中分离
                | wxpg.PG_NO_INTERNAL_BORDER # 无边框
                | wxpg.PG_TOOLBAR # 带工具条（用于切换不同的配置页）
                | wxpg.PG_DESCRIPTION # 带描述信息
                | wxpg.PGMAN_DEFAULT_STYLE # 启用默认风格
        )
        self.panelSizer.Add(self.pgPanel, 1, wx.EXPAND | wx.ALL, 5)
        self.pgPanel.ExtraStyle |= wxpg.PG_EX_HELP_AS_TOOLTIPS # 扩展工具栏帮助提示信息属性
        self.pgPanel.SetDescBoxHeight(99)

        self._init_pages()

    def _init_pages(self):
        '''初始化页面信息'''
        page = self.pgPanel.AddPage('基本')
        page.Append(wxpg.PropertyCategory(f'基本选项'))
        self.options: Dict[str, Any] = model_type_data.read_field_options()
        self.kwargs_datas = self.options # 预留
        for k, option in self.options.items():
            if not option.get("deprecated"):
                prop_type = option.get("type")
                prop_name = f'[{option["name"]}]-[{k}]'
                self.oriKeyName_combineName[k] = prop_name
                default = option["default"]
                if "StringProperty" == prop_type:
                    page.Append(wxpg.StringProperty(prop_name, value=default))
                elif "EnumProperty" == prop_type:
                    if 1 == len(default) and (default[0] in self.ALL_TEMPLATES_ARGS):
                        default = self.ALL_TEMPLATES_ARGS[default[0]]
                    '''
                        预留选项，用于取值
                    '''
                    self.kwargs_datas[k][CHOICES_DATA] = ['',]+default
                    page.Append(wxpg.EnumProperty(prop_name, prop_name, ['',]+default, list(range(len(default)+1)), 0))
                elif "ArrayStringProperty" == prop_type:
                    page.Append(wxpg.ArrayStringProperty(prop_name, value=default))
                elif "BoolProperty" == prop_type:
                    page.Append(wxpg.BoolProperty(prop_name, value=default))
                elif "IntProperty" == prop_type:
                    page.Append(wxpg.IntProperty(prop_name, value=default))
                elif "FloatProperty" == prop_type:
                    page.Append(wxpg.FloatProperty(prop_name, value=default))

        page = self.pgPanel.AddPage('额外')
        page.Append(wxpg.PropertyCategory(f'额外选项'))
        self.field_type: Dict[str, Any] = model_type_data.read_field_types()[self._get_fieldtype_key_by_value(self.field_name)]
        if not self.field_type["deprecated"]:
            extra_options: Dict[str, Any] = self.field_type["extra_options"]
            # self.kwargs_datas = self.kwargs_datas | extra_options # 为了兼容大部分版本，这里尽量不用语法糖
            self.kwargs_datas.update(extra_options)
            for k, option in extra_options.items():
                prop_type = option.get("type")
                prop_name = f'[{option["name"]}]-[{k}]'
                self.oriKeyName_combineName[k] = prop_name # 存储类似于 {'max_length': '[最大长度]-[max_length]'} 的数据，方便索引
                default = option["default"]
                if "StringProperty" == prop_type:
                    page.Append(wxpg.StringProperty(prop_name, value=default))
                elif "EnumProperty" == prop_type:
                    if 1 == len(default) and (default[0] in self.ALL_TEMPLATES_ARGS):
                        default = self.ALL_TEMPLATES_ARGS[default[0]]
                    '''
                        预留选项，用于取值
                    '''
                    self.kwargs_datas[k][CHOICES_DATA] = ['',]+default
                    page.Append(wxpg.EnumProperty(prop_name, prop_name, ['',]+default, list(range(len(default)+1)), 0))
                elif "ArrayStringProperty" == prop_type:
                    page.Append(wxpg.ArrayStringProperty(prop_name, value=default))
                elif "BoolProperty" == prop_type:
                    page.Append(wxpg.BoolProperty(prop_name, value=default))
                elif "IntProperty" == prop_type:
                    page.Append(wxpg.IntProperty(prop_name, value=default))
                elif "FloatProperty" == prop_type:
                    page.Append(wxpg.FloatProperty(prop_name, value=default))
        
        self.deepcopy_kwargs_datas = deepcopy(self.kwargs_datas) # 深拷贝，数据预存

        '''
            确保一致性，需要用属性面板的值再次刷新预存数据的默认值
        '''
        for _k, _v in self.pgPanel.GetPropertyValues(inc_attributes=True).items():
            self.deepcopy_kwargs_datas[self._get_key_from_name(_k)]["default"] = _v

    def _get_fieldtype_key_by_value(self, value: str)->str:
        '''通过【值】获取【键】
        
            如：通过 "字符型" 获取到 "CharField"
        '''
        for k, v in model_type_data.get_all_field_types_by_key('name'):
            if value == v:
                return k
        return None
            
    def onBtnSave(self, evt):
        '''保存字段
        
            保存前适当提醒【原则上，不会对用户的逻辑做任何控制】
        '''
        name_field = self.inputFieldName.GetValue().strip()
        if not name_field:
            RichMsgDialog.showOkMsgDialog(self, '保存前必须先命名字段', '错误')
            return

        if name_field in model_type_data.read_field_conflict_names():
            RichMsgDialog.showOkMsgDialog(self, '字段名非法，不允许使用冲突名称！！！', '错误')
            return

        field_type_code_name = self._get_fieldtype_key_by_value(self.field_name) # 字段类型代码名称 如 CharField
        
        '''
            冲突值比较
        '''
        conflict_info = self._check_link(field_type_code_name)
        if conflict_info[0]:
            RichMsgDialog.showScrolledMsgDialog(self, conflict_info[1], "参数冲突")
            return

        '''
            参数给定原则：
                默认值不参与构建，只构建改变过的值【源于数据包的比对】
        '''
        illigel_args = []
        struct_args = self._compare_args_combine_diff(illigel_args)
        if len(illigel_args) > 0:
            RichMsgDialog.showOkMsgDialog(self, f"{'、'.join(illigel_args)}参数必填！！！", "错误")
            return
        field_code = f"    {name_field} = models.{field_type_code_name}({struct_args})"
        if RichMsgDialog.showYesNoMsgDialog(self, f"正在为【{self.model_path}】路径下的【{self.model_name}】模型新增字段代码【{field_code}】", '提示'):
            djangotools.insert_field_into_model(self.model_path, self.model_name, field_code)
            '''
                进行缓存，未后续提示功能做数据准备
            '''

            self.model_fields.update({
                f'{name_field}': {
                    'ori_text': [
                        f"{field_code}",
                    ],
                    'options': {
                        
                    },
                    'type_name': f'{self._get_fieldtype_key_by_value(self.field_name)}'
                }
            })

            RichMsgDialog.showOkMsgDialog(self, f'【{name_field}】字段创建成功！', '成功')
            self.Close(True)

    def _check_link(self, field_type_name: str) -> Tuple[bool, str]:
        """参数设置冲突检查
        
            如：字段是 CharField 类型时，若 unique 是true，则 blank 也必须是 true。
            field_type_name: 如 CharField
            field_arg: 如 unique, null

            返回二元组：(是否冲突, 冲突信息)
        """
        msgs = []

        for field_arg_name in model_type_data.get_all_conflict_field_type_name():
            type_v = self.deepcopy_kwargs_datas.get(field_arg_name) # 找到对应的参数变量的值（一定有值！）
            attrDatas: dict = self.pgPanel.GetPropertyValues(inc_attributes=True) # 获取当前页面的所有数据包
            links: Dict[str, Dict[str, Any]] = type_v["links"] # 所有的关联关系

            # 判断是否是需要判断的条件
            flag = 0
            for _k, _v in links["condition"].items():
                if links["condition"][_k] != attrDatas[self.oriKeyName_combineName[_k]]:
                    flag = 1
                    break
            if 1 == flag:
                continue

            c_info: List[str] = []
            if field_type_name in links["fields"]: # 类型检测
                for con_k, con_v in links["constraint"].items():
                    if attrDatas[self.oriKeyName_combineName[con_k]] != con_v:
                        c_info.append(f"{con_k}={con_v}")
            
            if len(c_info) > 0:
                inner_msg = []
                for _k, _v in links["condition"].items():
                    inner_msg.append(f"{_k}={_v}")
                msg = f"当{', '.join(inner_msg)}时："
                msg += ", ".join(c_info)
                msg += " 必须同步设置！！！"
                msgs.append(msg)
                
        if len(msgs) > 0:
            return (True, '\n'.join(set(msgs)))
        else:
            return (False, '')

    def _compare_args_combine_diff(self, illigel_args: List[str]):
        '''比对两个数据包中不同的参数，予以组装返回【不同则表示修改过】
        
            如果必填项没有填写，则统计所有之后，返回错误参数，交由调用方处理。
        '''
        if self.deepcopy_kwargs_datas is None:
            return ''
        
        attrDatas: dict = self.pgPanel.GetPropertyValues(inc_attributes=True)

        temp: List[str] = []

        for k, v in self.deepcopy_kwargs_datas.items():
            if v['default'] != attrDatas[self.oriKeyName_combineName[k]]:
                value = attrDatas[self.oriKeyName_combineName[k]]
                if "EnumProperty" == v['type']: # 枚举：用索引获取实际的中文值
                    value = self.deepcopy_kwargs_datas[k][CHOICES_DATA][value]
                if "ArrayStringProperty" == v['type']:
                    value = [eval(_.strip()) for _ in value if _.strip()]
                if v["quot"]:
                    value = self._add_quot_around_string(value)
                temp.append(f"{k}={value}")
            else:
                if not v["optional"]: # 必填项检查
                    illigel_args.append(k)

        return ', '.join(temp)

    def onInputFieldName(self, evt):
        '''字段名文本实时监听'''
        new_value: str = evt.GetString()
        # 取属性列表全部值
        attrDatas: dict = self.pgPanel.GetPropertyValues(inc_attributes=True)
        # attrDatas["[可读名称]-[verbose_name]"] = retools.patt_template_replace(
        #     retools.PATT_REPLACE
        #     , attrDatas["[可读名称]-[verbose_name]"]
        #     , field_name_space = new_value.strip().replace("_", " ")
        # )
        attrDatas[self.oriKeyName_combineName["verbose_name"]] = new_value.strip().replace("_", " ")
        attrDatas[self.oriKeyName_combineName["db_column"]] = new_value.strip()

        '''
            初始值【仅字段名需要同步】
        '''
        self.deepcopy_kwargs_datas["verbose_name"]["default"] = new_value.strip().replace("_", " ")
        self.deepcopy_kwargs_datas["db_column"]["default"] = new_value.strip()

        self.pgPanel.SetPropertyValues(attrDatas)

    def _add_quot_around_string(self, string, quot_str: str="'"):
        '''为一个字符串两侧添加引号（用引号括起来）
        
            默认为单引号
        '''
        return f"{quot_str}{string}{quot_str}"

    def _get_key_from_name(self, name: str) -> str:
        """从选项名获取键值【规则为本工具自主约定的格式】
        
            如从：[最大长度]-[max_length] 中获取 max_length 。
        """
        try:
            return name.split('-')[1].strip('[]')
        except:
            return ''
            
    def onPgSelected(self, evt: wxpg.PropertyGridEvent):
        '''属性被选中
        
            抓取并显示提示信息
        '''
        if self.options is not None:
            name = evt.GetPropertyName()
            try:
                tips = "".join(self.options[self._get_key_from_name(name)]["tips"])
            except:
                try:
                    tips = "".join(self.field_type["extra_options"][self._get_key_from_name(name)]["tips"])
                except:
                    tips = ""
            self.pgPanel.SetDescription(name, tips)
